from fastapi import BackgroundTasks, Depends, FastAPI, Request
from urllib import parse
import time
import typing
from starlette.middleware.base import BaseHTTPMiddleware, DispatchFunction
from starlette.types import ASGIApp, Receive, Scope, Send
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint

from starlette.datastructures import URL, Headers
from starlette.responses import PlainTextResponse, RedirectResponse, Response
from starlette.types import ASGIApp, Receive, Scope, Send

#from app.common import exceptions
from app.common.enums.resp_enum import ErrTypes
#from app.config import conf
from app.utils import auth_util
from app.utils import resp_util
from app.utils.risk_util import is_ip_in_white_list
from app.common import get_redis

import logging

logger = logging.getLogger(__name__)


ENFORCE_DOMAIN_WILDCARD = "Domain wildcard patterns must be like '*.example.com'."

IP_WHITE_LIST = [
    '127.0.0.1',
    '131.22.35.22'
]


class SecCheckMiddleware:
    def __init__(
        self,
        app: ASGIApp,
        allowed_hosts: typing.Optional[typing.Sequence[str]] = None,
        allowed_uris: typing.Optional[typing.Sequence[str]] = None,
        www_redirect: bool = True,
    ) -> None:
        print('>> init SecCheckMiddleware')
        if allowed_hosts is None:
            allowed_hosts = ["*"]

        for pattern in allowed_hosts:
            assert "*" not in pattern[1:], ENFORCE_DOMAIN_WILDCARD
            if pattern.startswith("*") and pattern != "*":
                assert pattern.startswith("*."), ENFORCE_DOMAIN_WILDCARD
        self.app = app
        self.allowed_hosts = list(allowed_hosts)
        self.allowed_uris = list(allowed_uris)
        self.allow_any = "*" in allowed_hosts
        self.www_redirect = www_redirect

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        if self.allow_any or scope["type"] not in (
            "http",
            "websocket",
        ):  # pragma: no cover
            await self.app(scope, receive, send)
            return
        print('>> SecCheckMiddleware.__call__')
        r = get_redis()
        headers = Headers(scope=scope)
        host = headers.get("host", "").split(":")[0]
        is_valid_host = False
        is_allowed_uri = False
        found_www_redirect = False
        url = URL(scope=scope)
        url_path = url.path
        client = scope.get('client')
        client_ip = client[0]
        # IP白名单判断
        #if client_ip not in IP_WHITE_LIST:
        if not is_ip_in_white_list(r, client_ip):
            response: Response = resp_util.GenericErrResp(err_type=ErrTypes.forbidden_error , status_code=401, message='非法IP请求').get_response()
            return await response(scope, receive=receive, send=send)
            
        # URI地址白名单
        for url_pattern in self.allowed_uris:
            if url_path == url_pattern or (
                url_pattern.startswith("*") and url_path.endswith(url_pattern[1:])
            ) or (
                url_pattern.endswith("*") and url_path.startswith(url_pattern[:-1])
            ):
                is_allowed_uri = True
                break
        # 可信主机头判断(CORS)
        for pattern in self.allowed_hosts:
            if host == pattern or (
                pattern.startswith("*") and host.endswith(pattern[1:])
            ):
                is_valid_host = True
                break
            elif "www." + host == pattern:
                found_www_redirect = True

        if is_valid_host and is_allowed_uri:
            await self.app(scope, receive, send)
        else:
            # 主机头或URI不可信
            response: Response
            print('host: %s' % host)
            print('url.netloc: %s' % url.netloc)
            #response1 = PlainTextResponse("Silly Response\n%s" % err_text, status_code=401)
            response = resp_util.GenericErrResp(err_type=ErrTypes.forbidden_error , status_code=401).get_response()
            #response = resp_util.GenericErrResp().get_response()
            await response(scope, receive, send)
            return response
            #return resp_util.GenericErrResp().get_response()


class SignatureVerifyMiddleware(BaseHTTPMiddleware):

    unauth_uris: list = ['*']
    header_key = 'x-xpay-signature'

    def __init__(self, app: ASGIApp,
                 allowed_uris: list | None = None,
                 header_key: str | None = None,
                 ):
        super().__init__(app)
        if not allowed_uris:
            self.allowed_uris = ['*']
        self.header_key = header_key

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        if scope["type"] not in (
            "http",
            "websocket",
        ):  # pragma: no cover
            await self.app(scope, receive, send)
            return
        logger.debug('>> SignatureVerifyMiddleware.__call__')

    async def set_body(self, request: Request):
        receive_ = await request._receive()
        async def receive():
            return receive_
        request._receive = receive

    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint):
        print('>> dispatch')
        await self.set_body(request)
        body = await request.body()
        jsonbody = await request.json()
        id_ = jsonbody['external_id']
        response = await call_next(request)  
        print('>> before response')  
        return response


class RequestContextLogMiddleware(BaseHTTPMiddleware):

    async def set_body(self, request: Request):
        receive_ = await request._receive()

        async def receive():
            return receive_

        request._receive = receive

    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint):
        await self.set_body(request)
        body = await request.body()
        jsonbody = await request.json()
        id_ = jsonbody['external_id']
        response = await call_next(request)    
        return response
    

class CustomHeaderMiddleware(BaseHTTPMiddleware):
    def __init__(self, app, header_value='Example'):
        super().__init__(app)
        self.header_value = header_value

    async def dispatch(self, request, call_next):
        response = await call_next(request)
        response.headers['CustomH'] = self.header_value
        #response = await add_process_time_header(request, call_next)
        return response
    

#@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    print('---> start add_process_time_header')
    start_time = time.time()
    response = await call_next(request)
    process_time = time.time() - start_time
    response.headers["X-Process-Time"] = str(process_time)
    print('---> end add_process_time_header')
    return response

class SillyTestMiddleware:
    def __init__(
        self,
        app: ASGIApp,
        allowed_hosts: typing.Optional[typing.Sequence[str]] = None,
        allowed_uris: typing.Optional[typing.Sequence[str]] = None,
        www_redirect: bool = True,
    ) -> None:
        print('>> init SillyTestMiddleware')
        if allowed_hosts is None:
            allowed_hosts = ["*"]

        for pattern in allowed_hosts:
            assert "*" not in pattern[1:], ENFORCE_DOMAIN_WILDCARD
            if pattern.startswith("*") and pattern != "*":
                assert pattern.startswith("*."), ENFORCE_DOMAIN_WILDCARD
        self.app = app
        self.allowed_hosts = list(allowed_hosts)
        self.allowed_uris = list(allowed_uris)
        self.allow_any = "*" in allowed_hosts
        self.www_redirect = www_redirect

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        if self.allow_any or scope["type"] not in (
            "http",
            "websocket",
        ):  # pragma: no cover
            await self.app(scope, receive, send)
            return
        print('>> SillyTestMiddleware.__call__')
        headers = Headers(scope=scope)
        host = headers.get("host", "").split(":")[0]
        is_valid_host = False
        is_allowed_uri = False
        is_verified = False
        found_www_redirect = False
        url = URL(scope=scope)
        print('url split...')
        print('url scheme: %s' % url.scheme)
        print('url netloc: %s' % url.netloc)
        print('url port: %s' % url.port)
        print('url path: %s' % url.path)
        print('url query: %s' % url.query)
        parsed_query = parse.parse_qs(url.query)
        url_path = url.path
        err_text = 'unknown error'
        for url_pattern in self.allowed_uris:
            if url_path == url_pattern or (
                url_pattern.startswith("*") and url_path.endswith(url_pattern[1:])
            ) or (
                url_pattern.endswith("*") and url_path.startswith(url_pattern[:-1])
            ):
                is_allowed_uri = True
                break
            else:
                if parsed_query.get('token') and parsed_query.get('token')[0] == 'jessica':
                    is_verified = True
                else:
                    err_text = 'token有误!'
        '''
        if url.path not in self.allowed_uris:
            if parsed_query.get('token') and parsed_query.get('token')[0] == 'jessica':
                is_verified = True
            else:
                err_text = 'token有误!'
        else:
            is_allowed_uri = True
        '''
        for pattern in self.allowed_hosts:
            if host == pattern or (
                pattern.startswith("*") and host.endswith(pattern[1:])
            ):
                is_valid_host = True
                break
            elif "www." + host == pattern:
                found_www_redirect = True

        if is_valid_host and (is_allowed_uri or is_verified):
            await self.app(scope, receive, send)
        else:
            response: Response
            #print('url.netloc: %s' % url.netloc)
            response = PlainTextResponse("Silly Response\n%s" % err_text, status_code=401)
            await response(scope, receive, send)
